/*
 * Copyright (c) 2010, Katholieke Universiteit Leuven
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *     * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 *     * Neither the name of the Katholieke Universiteit Leuven nor the names of
 *       its contributors may be used to endorse or promote products derived from
 *       this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/**
 * @addtogroup commands
 * @{
 */
/**
 * @file
 * LooCI Command Interface
 * @author
 * Wouter Horré <wouter.horre@cs.kuleuven.be>
 */


#include "looci.h"
#include "contiki-net.h"
#include "components/components.h"
#include "components/components_private.h"
#include "reconfiguration/runtime_control_local.h"
#include "reconfiguration/introspection_local.h"
#include "reconfiguration/componentstore_private.h"
#include "net/peers.h"
//#include "events/events_private.h"

#include <string.h>
#include <stdio.h>
#include <inttypes.h>
#include <stdbool.h>

#include "commands_private.h"

#ifdef LOOCI_COMMANDS_DEBUG
#include <stdio.h>
#ifdef CONTIKI_TARGET_AVR_RAVEN
#include <avr/pgmspace.h>
#define PRINTF(FORMAT,args...) printf_P(PSTR(FORMAT),##args)
#else
#define PRINTF printf
#endif // CONTIKI_TARGET_AVR_RAVEN
#define PRINT6ADDR(addr) PRINTF(" %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x ", ((u8_t *)addr)[0], ((u8_t *)addr)[1], ((u8_t *)addr)[2], ((u8_t *)addr)[3], ((u8_t *)addr)[4], ((u8_t *)addr)[5], ((u8_t *)addr)[6], ((u8_t *)addr)[7], ((u8_t *)addr)[8], ((u8_t *)addr)[9], ((u8_t *)addr)[10], ((u8_t *)addr)[11], ((u8_t *)addr)[12], ((u8_t *)addr)[13], ((u8_t *)addr)[14], ((u8_t *)addr)[15])
#else
#define PRINTF(...)
#define PRINT6ADDR(addr)
#endif

#define UIP_IP_BUF   ((struct uip_udpip_hdr *)&uip_buf[UIP_LLH_LEN])


struct inc_wire_net {
  uip_ip6addr_t addr;
  u8_t source_cid;
};

// DEPLOYMENT RELATED
static bool depl_remove(u8_t cid) {
  PRINTF("[LOOCI COMMANDS] Handling remove command\r\n");
  struct component * c = components_get(cid);
  if(c!=NULL) {
    if(c->state == COMPONENT_STATE_ACTIVE) {
      components_stop(cid);
    }
    cstore_unload(cid);
    return true;
  } else {
    return false;
  }
}
// END DEPLOYMENT RELATED

static void handle_command(struct command_request * input, size_t iplen, struct command_reply * output, size_t * oplen) {
  PRINTF("[LOOCI COMMANDS] Received a command\r\n");
  PRINTF("[LOOCI COMMANDS] Seq. nr. is %u\r\n", input->seq_nr);
  // just echo the seq. nr
  output->seq_nr = input->seq_nr;
  // branch based on the command
  // Deployment related
  if(input->cmd == COMMAND_REMOVE) {
    bool result = false;
    // execute command
    if(iplen >= 1) {
      result = depl_remove(input->payload[0]);
    }
    // send back result
    output->payload[0] = (u8_t) result;
    *oplen = 1;
    return;
  } // Runtime control related 
  else if(input->cmd == COMMAND_DEACTIVATE || input->cmd == COMMAND_ACTIVATE || input->cmd == COMMAND_RESET_WIRINGS) {
    bool result = false;
    if(iplen >= 1) { // we allow for some junk at the end
      u8_t cid = input->payload[0];
      if(input->cmd == COMMAND_DEACTIVATE) result = looci_rc_deactivate(cid);
      else if(input->cmd == COMMAND_ACTIVATE) result = looci_rc_activate(cid);
      else if(input->cmd == COMMAND_RESET_WIRINGS) result = looci_rc_reset_wirings(cid);
    }
    // send back the result
    output->payload[0] = (u8_t) result;
    *oplen = 1;
    return;
  } else if(input->cmd == COMMAND_WIRE_LOCAL || input->cmd == COMMAND_UNWIRE_LOCAL) {
    bool result = false;
    if(iplen >= 3) {
      if(input->cmd == COMMAND_WIRE_LOCAL) result = looci_rc_wire_local(input->payload[0], input->payload[1], input->payload[2]);
      else if(input->cmd == COMMAND_UNWIRE_LOCAL) result = looci_rc_unwire_local(input->payload[0], input->payload[1], input->payload[2]);
    }
    // send back the result
    output->payload[0] = (u8_t) result;
    *oplen = 1;
    return;
  } else if(input->cmd == COMMAND_WIRE_REMOTE_FROM || input->cmd == COMMAND_UNWIRE_REMOTE_FROM) {
    bool result = false;
    if(iplen >= 19) {
      peer_id_t snode = peer_get_id_or_add((peer_addr_t *)(input->payload + 1));
      if(input->cmd == COMMAND_WIRE_REMOTE_FROM) 
        result = looci_rc_wire_remote_from(input->payload[0], snode, input->payload[17], input->payload[18]);
      else if(input->cmd == COMMAND_UNWIRE_REMOTE_FROM)
        result = looci_rc_unwire_remote_from(input->payload[0], snode, input->payload[17], input->payload[18]);
    }
    // send back the result
    output->payload[0] = (u8_t) result;
    *oplen = 1;
    return;
  } else if(input->cmd == COMMAND_WIRE_REMOTE_TO || input->cmd == COMMAND_UNWIRE_REMOTE_TO) {
    bool result = false;
    if(iplen >= 18) {
      peer_id_t dnode = peer_get_id_or_add((peer_addr_t*)(input->payload + 2));
      if(input->cmd == COMMAND_WIRE_REMOTE_TO) 
        result = looci_rc_wire_remote_to(input->payload[0], input->payload[1], dnode);
      else if(input->cmd == COMMAND_UNWIRE_REMOTE_TO)
        result = looci_rc_unwire_remote_to(input->payload[0], input->payload[1], dnode);
    }
    // send back the result
    output->payload[0] = (u8_t) result;
    *oplen = 1;
    return;
  } else if(input->cmd == COMMAND_WIRE_REMOTE_FROM_ALL || input->cmd == COMMAND_UNWIRE_REMOTE_FROM_ALL) {
    bool result = false;
    if(iplen >= 2) {
      if(input->cmd == COMMAND_WIRE_REMOTE_FROM_ALL)
        result = looci_rc_wire_remote_from_all(input->payload[0], input->payload[1]);
      else if(input->cmd == COMMAND_UNWIRE_REMOTE_FROM_ALL)
        result = looci_rc_unwire_remote_from_all(input->payload[0], input->payload[1]);
    }
    // send back the result
    output->payload[0] = (u8_t) result;
    *oplen = 1;
    return;
  } else if(input->cmd == COMMAND_WIRE_REMOTE_TO_ALL || input->cmd == COMMAND_UNWIRE_REMOTE_TO_ALL) {
    bool result = false;
    if(iplen >= 2) {
      if(input->cmd == COMMAND_WIRE_REMOTE_TO_ALL)
        result = looci_rc_wire_remote_to_all(input->payload[0], input->payload[1]);
      else if(input->cmd == COMMAND_UNWIRE_REMOTE_TO_ALL)
        result = looci_rc_unwire_remote_to_all(input->payload[0], input->payload[1]);
    }
    // send back the result
    output->payload[0] = (u8_t) result;
    *oplen = 1;
    return;
  } // Introspection related
  else if(input->cmd == COMMAND_GET_COMPONENTS_TYPE) {
    bool result = false;
    // initialize output
    output->payload[1] = *oplen - 2;
    if(iplen >= 1) {
      result = looci_intro_getcomponentids_by_type((char*)input->payload, output->payload + 2, output->payload + 1);
    }
    // send back result
    output->payload[0] = (u8_t) result;
    *oplen = output->payload[1] + 2;
    return;
  } else if(input->cmd == COMMAND_GET_COMPONENTS_ALL) {
    bool result = false;
    // initialize output
    output->payload[1] = *oplen - 2;
    result = looci_intro_getcomponentids(output->payload + 2, output->payload + 1);
    // send back result
    output->payload[0] = (u8_t) result;
    *oplen = output->payload[1] + 2;
    return;
  } else if(input->cmd == COMMAND_GET_COMPONENTTYPE) {
    bool result = false;
    if(iplen >= 1) {
      result = looci_intro_getcomponenttype(input->payload[0], (char*)(output->payload + 1), *oplen - 1);
    }
    // send back result
    output->payload[0] = (u8_t) result;
    *oplen = strlen((char*)(output->payload + 1)) + 2; // + 2: 1 for result, 1 for terminating null character
    return;
  } else if(input->cmd == COMMAND_GET_STATE) {
    u8_t state = COMPONENT_STATE_NONE;
    if(iplen >= 1) {
      state = looci_intro_getstate(input->payload[0]);
    }
    // send back state
    output->payload[0] = state;
    *oplen = 1;
    return;
  } else if(input->cmd == COMMAND_GET_INTERFACES || input->cmd == COMMAND_GET_RECEPTACLES) {
    bool result = false;
    // Initialize output
    output->payload[1] = *oplen - 2;
    if(iplen >= 1) {
      if(input->cmd == COMMAND_GET_INTERFACES) result = looci_intro_getinterfaces(input->payload[0], output->payload + 2, output->payload + 1);
      else if(input->cmd == COMMAND_GET_RECEPTACLES) result = looci_intro_getreceptacles(input->payload[0], output->payload + 2, output->payload + 1);
    }
    // send back result
    output->payload[0] = (u8_t) result;
    *oplen = output->payload[1] + 2;
    return;
  } else if(input->cmd == COMMAND_GET_OUTGOING_WIRES) {
    bool result = false;
    // initialize output
    output->payload[1] = (*oplen - 2) / sizeof(uip_ip6addr_t); // integer division
    if(iplen >= 2) {
      peer_id_t pbuf[output->payload[1]];
      result = looci_intro_getoutgoingremotewires(input->payload[0], input->payload[1], pbuf, output->payload + 1);
      u8_t i = 0;
      uip_ip6addr_t * wires = (uip_ip6addr_t *) (output->payload + 2);
      for(i=0; i < output->payload[1]; ++i) {
        uip_ip6addr_t * addr = peer_get_addr(pbuf[i]);
        if(addr != NULL) { // better be safe than sorry
          PRINTF("[LooCI Commands] Copying ip6 address to output\r\n");
          memcpy(wires + i, addr, sizeof(uip_ip6addr_t));
          PRINT6ADDR(&(wires[i])); PRINTF("\r\n");
        }
      }
    }
    // send back result
    output->payload[0] = (u8_t)result;
    *oplen = (output->payload[1] * sizeof(uip_ip6addr_t)) + 2;
    return;
  } else if(input->cmd == COMMAND_GET_INCOMING_WIRES) {
    bool result = false;
    output->payload[1] = 0;
    // initialize output
    if(iplen >= 2) {
      PRINTF("[LooCI Commands] Preparing to call getincomingremotewires\r\n");
      output->payload[1] = (*oplen - 2) / sizeof(struct inc_wire_net); // integer division
      struct wire_from wbuf[output->payload[1]];
      result = looci_intro_getincomingremotewires(input->payload[0], input->payload[1], wbuf, output->payload + 1);
      u8_t i = 0;
      struct inc_wire_net * wires = (struct inc_wire_net *) (output->payload + 2);
      for(i=0; i < output->payload[1]; ++i) {
        uip_ip6addr_t * addr = peer_get_addr(wbuf[i].source_node);
        if(addr!=NULL) {
          memcpy(&(wires[i].addr), addr, sizeof(uip_ip6addr_t));
        } else {
          memset(&(wires[i].addr), 0, sizeof(uip_ip6addr_t));
        }
        wires[i].source_cid = wbuf[i].source_cid;
      }
    }
    // send back result
    PRINTF("[LooCI Commands] The call to getincomingremotewires returned %u\r\n", result);
    output->payload[0] = (u8_t) result;
    *oplen = (output->payload[1] * (sizeof(uip_ip6addr_t) + sizeof(u8_t))) + 2; // +2: for the bool result and the length
    return;
  } else if(input->cmd == COMMAND_GET_LOCAL_WIRES) {
    bool result = false;
    // Initialize output
    output->payload[1] = *oplen - 2;
    if(iplen >= 2) {
      result = looci_intro_getlocalwires(input->payload[0], input->payload[1], output->payload + 2, output->payload + 1);
    }
    // send back result
    output->payload[0] = (u8_t) result;
    *oplen = output->payload[1] + 2;
    return;
  }
}

// NETWORK RELATED STUFF
// CURRENTLY UDP

static struct uip_udp_conn * udpconn;
static u8_t obuf[LOOCI_COMMANDS_BUFSIZE + sizeof(struct command_reply)];

#ifdef WITH_COMMANDS_CLIENT
static struct uip_udp_conn * clientudpconn;
#endif

#ifdef WITH_COMMANDS_CLIENT
// COMMAND CLIENT
bool looci_commands_send(peer_id_t peer, struct command_request * request, u8_t payload_len) {
  if(payload_len <= 0 || clientudpconn == NULL) {
    PRINTF("[LooCI Commands] fatal error while trying to send a command\r\n");
    return false;
  }
  peer_addr_t * peeraddr = peer_get_addr(peer);
  if(peeraddr == NULL) {
    return false;
  }
  PRINTF("[LOOCI COMMANDS] Sending a request\r\n");
  request->seq_nr = components_get_by_process(PROCESS_CURRENT())->id;
  uip_udp_packet_sendto(clientudpconn, request, sizeof(struct command_request) + payload_len,
      (uip_ipaddr_t*)peeraddr, HTONS(LOOCI_COMMANDS_PORT));
  return true;
}
#endif


static void send_reply(u8_t * output, size_t len) {
  if(len <= 0) {
    return;
  }
  PRINTF("[LOOCI COMMANDS] Sending reply\r\n");
  uip_udp_packet_sendto(udpconn, output, len, &UIP_IP_BUF->srcipaddr, UIP_IP_BUF->srcport);
}

PROCESS(looci_commands, "LooCI Command Interface");
PROCESS_THREAD(looci_commands, ev, data)
{
  PROCESS_BEGIN();

  PRINTF("[LOOCI COMMANDS] starting\r\n");

  udpconn = udp_new(NULL, 0, NULL);
  udp_bind(udpconn, HTONS(LOOCI_COMMANDS_PORT));

#ifdef WITH_COMMANDS_CLIENT
  clientudpconn = udp_new(NULL, HTONS(LOOCI_COMMANDS_PORT), NULL);
#endif

  while(1) {
    PROCESS_WAIT_EVENT();
    if(ev == tcpip_event && uip_udp_conn == udpconn && uip_newdata()) {
      size_t olen = LOOCI_COMMANDS_BUFSIZE; // initialize to max we can accomodate
      handle_command((struct command_request *)uip_appdata, uip_datalen()-sizeof(struct command_request), (struct command_reply *) obuf, &olen);
      send_reply(obuf, olen+sizeof(struct command_reply));
    }
#ifdef WITH_COMMANDS_CLIENT
    else if(ev == tcpip_event && uip_udp_conn == clientudpconn && uip_newdata()) {
      PRINTF("[LooCI Commands] Got a reply of length %u\r\n", uip_datalen());
      struct looci_command_reply_event_data data;
      data.len = uip_datalen();
      data.reply = (struct command_reply *) uip_appdata;
      struct process * destproc = (components_get(data.reply->seq_nr))->process;
      process_post_synch(destproc, looci_command_reply_event, &data);
    }
#endif
  }

  PROCESS_END();
}

/** @} */
